1 module targets.android; 2 import commons; 3 import std.net.curl; 4 import std.path; 5 6 ///This is the one which will be installed when using the SDK. 7 enum TargetAndroidSDK = 31; 8 enum TargetAndroidNDK = "21.4.7075529"; 9 enum Ldc2AndroidAarchLibReleaseLink = "https://github.com/MrcSnm/HipremeEngine/releases/download/BuildAssets.v1.0.0/android.zip"; 10 enum CurrentlySupportedLdc2Version = "ldc2 1.33.0-beta1"; 11 ///Use a random Adb Port 12 enum HipremeEngineAdbPort = "55565"; 13 14 enum FindAndroidNdkResult 15 { 16 NotFound, 17 Found, 18 MustInstallSdk, 19 MustInstallNdk 20 } 21 22 private FindAndroidNdkResult tryFindAndroidNDK(ref Terminal t, ref RealTimeConsoleInput input) 23 { 24 if("ANDROID_NDK_HOME" in environment) 25 { 26 configs["androidNdkPath"] = environment["ANDROID_NDK_HOME"]; 27 string sdk = getFirstExistingVar("ANDROID_SDK", "ANDROID_SDK_HOME"); 28 if(!sdk.length) 29 sdk = buildNormalizedPath(configs["androidNdkPath"].str, "..", ".."); 30 configs["androidSdkPath"] = sdk; 31 return FindAndroidNdkResult.Found; 32 } 33 bool isValidNDK(string chosenNDK) 34 { 35 import std.conv:to; 36 int ndkVer = chosenNDK[0..2].to!int; 37 return ndkVer <= 21; 38 } 39 version(Windows) 40 { 41 string locAppData = environment["LOCALAPPDATA"]; 42 if(locAppData == null) 43 { 44 t.writelnError("Could not find %LOCALAPPDATA% in your Windows."); 45 t.flush; 46 return FindAndroidNdkResult.NotFound; 47 } 48 string sdkPath = buildNormalizedPath(locAppData, "Android", "Sdk"); 49 string tempNdkPath = sdkPath; 50 51 if(!std.file.exists(sdkPath)) 52 { 53 t.writelnError("Could not find ", sdkPath, ". You need to install Android SDK."); 54 t.flush; 55 return FindAndroidNdkResult.MustInstallSdk; 56 } 57 tempNdkPath = buildNormalizedPath(sdkPath, "ndk"); 58 if(!std.file.exists(tempNdkPath)) 59 { 60 t.writelnError("Could not find ", tempNdkPath, ". You need to have at least one NDK installed."); 61 t.flush; 62 return FindAndroidNdkResult.MustInstallNdk; 63 } 64 do 65 { 66 string ndkPath = selectInFolder( 67 "Select the NDK which you want to use. Remember that only NDK <= 21 is supported.", 68 tempNdkPath, t, input 69 ); 70 if(isValidNDK(ndkPath)) 71 { 72 tempNdkPath = ndkPath; 73 break; 74 } 75 t.writelnError("Please select a valid NDK (<= 21)"); 76 } while(true); 77 t.writelnSuccess("Chosen "~ tempNdkPath~ " as your NDK."); 78 environment["androidNdkPath"] = sdkPath; 79 environment["androidSdkPath"] = tempNdkPath; 80 return FindAndroidNdkResult.Found; 81 } 82 else version(linux) 83 { 84 return FindAndroidNdkResult.NotFound; 85 } 86 else version(OSX) 87 { 88 return FindAndroidNdkResult.NotFound; 89 } 90 91 } 92 93 private string getAndroidSDKDownloadLink() 94 { 95 version(Windows) return "https://dl.google.com/android/repository/commandlinetools-win-9477386_latest.zip"; 96 else version(linux) return "https://dl.google.com/android/repository/commandlinetools-linux-9477386_latest.zip"; 97 else version(OSX) return "https://dl.google.com/android/repository/commandlinetools-mac-9477386_latest.zip"; 98 else assert(false, "Your system does not have an Android SDK."); 99 } 100 101 private string getOpenJDKDownloadLink() 102 { 103 version(Windows) return "https://aka.ms/download-jdk/microsoft-jdk-11.0.18-windows-x64.zip"; 104 else version(linux) return "https://download.java.net/java/GA/jdk11/9/GPL/openjdk-11.0.2_linux-x64_bin.tar.gz"; 105 else version(OSX) return "https://download.java.net/java/GA/jdk11/13/GPL/openjdk-11.0.1_osx-x64_bin.tar.gz"; 106 else assert(false, "Your system does not have an OpenJDK"); 107 } 108 109 private string getAndroidFlagsToolchains() 110 { 111 version(Windows) 112 { 113 import std.string:replace; 114 return "-gcc=\""~buildNormalizedPath(configs["androidNdkPath"].str, "toolchains/llvm/prebuilt/windows-x86_64/bin/aarch64-linux-android21-clang.cmd").replace("\\", "/") ~"\" " ~ 115 "-linker=\""~buildNormalizedPath(configs["androidNdkPath"].str, "toolchains/llvm/prebuilt/windows-x86_64/bin/aarch64-linux-android-ld.bfd.exe").replace("\\", "/") ~"\" " ~ 116 ///Put the lib path for finding libandroid, liblog, libOpenSLES, libEGL and libGLESv3 117 "-L-L\""~buildNormalizedPath(configs["androidNdkPath"].str, "toolchains/llvm/prebuilt/windows-x86_64/sysroot/usr/lib/aarch64-linux-android/30/").replace("\\", "/")~"\" " 118 ; 119 } 120 else version(linux) 121 { 122 return "-gcc=\""~buildNormalizedPath(configs["androidNdkPath"].str, "toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang") ~"\" " ~ 123 "-linker=\""~buildNormalizedPath(configs["androidNdkPath"].str, "toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android-ld.bfd") ~"\" " ~ 124 ///Put the lib path for finding libandroid, liblog, libOpenSLES, libEGL and libGLESv3 125 "-L-L\""~buildNormalizedPath(configs["androidNdkPath"].str, "toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/lib/aarch64-linux-android/30/")~"\" " 126 ; 127 } 128 else version(OSX) 129 { 130 return "-gcc=\""~buildNormalizedPath(configs["androidNdkPath"].str, "toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android21-clang") ~"\" " ~ 131 "-linker=\""~buildNormalizedPath(configs["androidNdkPath"].str, "toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android-ld.bfd") ~"\" " ~ 132 ///Put the lib path for finding libandroid, liblog, libOpenSLES, libEGL and libGLESv3 133 "-L-L\""~buildNormalizedPath(configs["androidNdkPath"].str, "toolchains/llvm/prebuilt/darwin-x86_64/sysroot/usr/lib/aarch64-linux-android/30/")~"\" " 134 ; 135 } 136 } 137 138 private string getPackagesToInstall() 139 { 140 import std.conv:to; 141 string packages = `"build-tools;`~to!string(TargetAndroidSDK)~`.0.0" `~ 142 `"extras;google;webdriver" ` ~ 143 `"platform-tools" ` ~ 144 `"ndk;`~TargetAndroidNDK~`" `~ 145 `"platforms;android-`~to!string(TargetAndroidSDK)~`" `~ 146 `"sources;android-`~to!string(TargetAndroidSDK)~`" `; 147 148 version(Windows) 149 { 150 packages~= `"extras;intel;Hardware_Accelerated_Execution_Manager" `~ 151 `"extras;google;usb_driver" `; 152 } 153 return packages; 154 } 155 156 157 private bool downloadOpenJDK(ref Terminal t, ref RealTimeConsoleInput input) 158 { 159 string javaContainer = "openjdk_11.zip"; 160 version(Posix) javaContainer = javaContainer.setExtension(".tar.gz"); 161 javaContainer = buildNormalizedPath(std.file.tempDir, javaContainer); 162 downloadFileIfNotExists("OpenJDK for building to Android. ", getOpenJDKDownloadLink(), javaContainer, t, input); 163 164 string outputPath = buildNormalizedPath(std.file.getcwd(), "Android", "openjdk_11"); 165 return extractToFolder(javaContainer, outputPath, t, input); 166 } 167 168 private bool downloadAndroidSDK(ref Terminal t, ref RealTimeConsoleInput input, out string sdkPath) 169 { 170 import std.file:tempDir; 171 import std.zip; 172 173 string androidSdkZip = buildNormalizedPath(tempDir, "android_sdk.zip"); 174 175 if(!downloadFileIfNotExists("Android SDK will be installed on your system, do you accept it?", 176 getAndroidSDKDownloadLink(), androidSdkZip, t, input)) 177 return false; 178 179 string outputDirectory = buildNormalizedPath(std.file.getcwd(), "Android", "Sdk"); 180 sdkPath = outputDirectory; 181 string finalOutput = buildNormalizedPath(outputDirectory, "cmdline-tools", "latest"); 182 183 if(!std.file.exists(finalOutput)) 184 { 185 if(!extractZipToFolder(androidSdkZip, outputDirectory, t)) 186 return false; 187 std.file.rename(buildNormalizedPath(outputDirectory, "cmdline-tools/"), buildNormalizedPath(outputDirectory, "latest/")); 188 std.file.mkdirRecurse(buildNormalizedPath(outputDirectory, "cmdline-tools")); 189 std.file.rename(buildNormalizedPath(outputDirectory, "latest"), finalOutput); 190 } 191 return true; 192 } 193 194 private bool downloadAndroidLibraries(ref Terminal t, ref RealTimeConsoleInput input) 195 { 196 string androidZipDir = buildNormalizedPath(std.file.tempDir(), "androidLibs.zip"); 197 if(!downloadFileIfNotExists("Do you accept downloading android libraries for "~CurrentlySupportedLdc2Version, 198 Ldc2AndroidAarchLibReleaseLink, androidZipDir, t, input)) 199 return false; 200 201 string outputDir = buildNormalizedPath(std.file.getcwd(), "Android", "ldcLibs"); 202 extractZipToFolder(androidZipDir, outputDir, t); 203 204 return true; 205 } 206 207 208 private bool installAndroidNDK(ref Terminal t, string sdkPath) 209 { 210 string finalOutput = buildNormalizedPath(sdkPath, "cmdline-tools", "latest"); 211 t.writeln("Updating SDK manager."); 212 t.flush; 213 214 215 string sdkManagerPath = buildNormalizedPath(finalOutput, "bin"); 216 217 if(!makeFileExecutable(buildNormalizedPath(sdkManagerPath, "sdkmanager"))) 218 { 219 t.writeln("Failed to set sdkmanager as executable."); 220 t.flush; 221 return false; 222 } 223 224 string execSdkManager = "sdkmanager "; 225 version(Posix) execSdkManager = "./sdkmanager"; 226 227 if(wait(spawnShell("cd "~sdkManagerPath~" && "~execSdkManager~" --install")) != 0) 228 { 229 t.writeln("Failed on installing SDK."); 230 t.flush; 231 return false; 232 } 233 234 t.writeln("Installing packages: ", getPackagesToInstall()); 235 t.writeln("You will need to accept some permissions, this process may take a little bit of time."); 236 t.flush; 237 if(wait(spawnShell("cd "~sdkManagerPath~" && "~execSdkManager ~" " ~getPackagesToInstall())) != 0) 238 { 239 t.writeln("Failed on installing NDK."); 240 t.flush; 241 return false; 242 } 243 if(!makeFileExecutable(buildNormalizedPath(sdkPath, "platform-tools", "adb"))) 244 { 245 t.writeln("Failed to set adb as executable."); 246 t.flush; 247 return false; 248 } 249 250 configs["androidSdkPath"] = sdkPath; 251 configs["androidNdkPath"] = buildNormalizedPath(sdkPath, "ndk", TargetAndroidNDK); 252 updateConfigFile(); 253 return true; 254 } 255 256 private bool installOpenJDK(ref Terminal t, ref RealTimeConsoleInput input) 257 { 258 if(!("javaHome" in configs)) 259 { 260 if(!("JAVA_HOME" in environment)) 261 { 262 t.writelnHighlighted("JAVA_HOME wasn't found in your environment. 263 Build Selector will download a compatible OpenJDK for Android Development."); 264 t.flush; 265 if(!downloadOpenJDK(t, input)) 266 { 267 t.writelnError("Could not download OpenJDK"); 268 return false; 269 } 270 string javaHome = buildNormalizedPath(std.file.getcwd(), "Android", "openjdk_11"); 271 version(Windows) javaHome = buildNormalizedPath(javaHome, "jdk-11.0.18+10"); 272 else version(linux) javaHome = buildNormalizedPath(javaHome, "jdk-11.0.2"); 273 else version(OSX) javaHome = buildNormalizedPath(javaHome, "jdk-11.0.1.jdk", "Contents", "Home"); 274 else assert(false, "Your OS is not supported."); 275 if(!std.file.exists(javaHome)) 276 { 277 t.writelnError("Expected JAVA_HOME at automatic installation does not exists:" ~ javaHome); 278 t.flush(); 279 return false; 280 } 281 configs["javaHome"] = javaHome; 282 updateConfigFile(); 283 } 284 else 285 { 286 configs["javaHome"] = environment["JAVA_HOME"]; 287 updateConfigFile(); 288 } 289 } 290 return true; 291 } 292 293 private bool installAndroidSDK(ref Terminal t, ref RealTimeConsoleInput input) 294 { 295 if(!("androidNdkPath" in configs) || !("androidSdkPath" in configs)) 296 { 297 FindAndroidNdkResult res = tryFindAndroidNDK(t, input); 298 switch(res) 299 { 300 case FindAndroidNdkResult.NotFound: 301 { 302 string sdkPath; 303 if(!downloadAndroidSDK(t, input, sdkPath)) 304 { 305 t.writelnError("Android SDK download didn't succeed"); 306 return false; 307 } 308 if(!installAndroidNDK(t, sdkPath)) 309 { 310 t.writelnError("Could not install Android NDK."); 311 return false; 312 } 313 break; 314 } 315 case FindAndroidNdkResult.Found: 316 updateConfigFile(); 317 break; 318 default: assert(false, "Case not yet implemented."); 319 } 320 } 321 return true; 322 } 323 324 325 private void runAndroidApplication(ref Terminal t) 326 { 327 version(Windows) 328 { 329 string adb = buildNormalizedPath(configs["androidSdkPath"].str, "platform-tools", "adb.exe"); 330 string gradlew = "gradlew.bat"; 331 } 332 else version(Posix) 333 { 334 string adb = buildNormalizedPath(configs["androidSdkPath"].str, "platform-tools", "adb"); 335 string gradlew = "./gradlew"; 336 337 if(!makeFileExecutable(buildNormalizedPath("build", "android", "project", "gradlew"))) 338 { 339 t.writelnError("Could not make gradlew executable."); 340 return; 341 } 342 } 343 344 std.file.chdir(buildNormalizedPath("build", "android", "project")); 345 346 wait(spawnShell(gradlew ~ " :app:assembleDebug")); 347 348 string adbInstall = adb~" install -r "~ 349 buildNormalizedPath(std.file.getcwd(), "app", "build", "outputs", "apk", "debug", "app-debug.apk"); 350 351 t.writeln("Executing adb install: ", adbInstall); 352 t.flush; 353 environment["ANDROID_ADB_SERVER_PORT"] = HipremeEngineAdbPort; 354 wait(spawnShell(adbInstall)); 355 wait(spawnShell(adb~" shell monkey -p com.hipremeengine.app 1")); 356 } 357 358 ChoiceResult prepareAndroid(Choice* c, ref Terminal t, ref RealTimeConsoleInput input, in CompilationOptions cOpts) 359 { 360 if(!installOpenJDK(t, input)) 361 { 362 t.writelnError("Failed installing OpenJDK."); 363 return ChoiceResult.Error; 364 } 365 environment["JAVA_HOME"] = configs["javaHome"].str; 366 if(!installAndroidSDK(t, input)) 367 { 368 t.writelnError("Failed installing Android SDK."); 369 return ChoiceResult.Error; 370 } 371 372 373 if(!std.file.exists( 374 buildNormalizedPath(std.file.getcwd(), "Android", "ldcLibs", "android", "lib", "libdruntime-ldc.a"))) 375 { 376 if(!downloadAndroidLibraries(t, input)) 377 { 378 t.writelnError("Failed downloading ldc android libraries."); 379 t.flush; 380 return ChoiceResult.Error; 381 } 382 } 383 environment["ANDROID_HOME"] = configs["androidSdkPath"].str; 384 385 runEngineDScript(t, "releasegame.d", configs["gamePath"].str); 386 putResourcesIn(t, getHipPath("build", "android", "project", "app", "src", "main", "assets")); 387 cached(() => timed(() => outputTemplateForTarget(t))); 388 389 string ldcLibsPath = buildNormalizedPath(std.file.getcwd(), "Android", "ldcLibs", "android", "lib"); 390 391 392 string nextReleaseFlags = "-defaultlib=phobos2-ldc,druntime-ldc " ~ 393 "-link-defaultlib-shared=false " ~ 394 "-L-L\""~ ldcLibsPath ~"\" " ~ 395 "-L-rpath=\""~ ldcLibsPath~"\" "; 396 397 environment["DFLAGS"] = nextReleaseFlags ~ getAndroidFlagsToolchains(); 398 t.writeln(environment["DFLAGS"]); 399 t.flush; 400 401 std.file.chdir(configs["hipremeEnginePath"].str); 402 if(waitDubTarget(t, "android", DubArguments().command("build").arch("aarch64--linux-android").opts(cOpts)) != 0) 403 { 404 t.writelnError("Compilation failed."); 405 return ChoiceResult.Error; 406 } 407 408 std.file.rename( 409 buildNormalizedPath("bin", "android", "libhipreme_engine.so"), 410 buildNormalizedPath("build", "android", "project", "app", "src", "main", "jniLibs", "arm64-v8a", "libhipreme_engine.so") 411 ); 412 runAndroidApplication(t); 413 414 return ChoiceResult.Continue; 415 }